﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SimpleTaskManager.Domain.Abstract;
using SimpleTaskManager.Domain.Entities;
using System.Web.Security;
using SimpleTaskManager.WebUI.Models;

namespace SimpleTaskManager.WebUI.Controllers
{
    /// <summary>
    /// A controller responsible for managing users
    /// </summary>
    public class UserController : Controller
    {
        private IUserRepository repository;
        private IWorkingHoursEntryRepository hoursRep;
        private IPasswordEncrypter passwordEncrypter;
        private ISkillRepository skillsRepo;
        private IUser_SkillRepository userSkillRepo;

        /// <summary>
        /// Creates a new instance of the UserController.
        /// </summary>
        /// <param name="userRepository">A repository containing the users.</param>
        /// <param name="hours">A repository containing the working hour entries.</param>
        /// <param name="encrypter">A password encrypter.</param>
        /// <param name="skills">A repository containing the skills.</param>
        /// <param name="userSkills">A repository containing the user skills.</param>
        public UserController(IUserRepository userRepository, IWorkingHoursEntryRepository hours,
            IPasswordEncrypter encrypter, ISkillRepository skills, IUser_SkillRepository userSkills)
        {
            repository = userRepository;
            hoursRep = hours;
            passwordEncrypter = encrypter;
            skillsRepo = skills;
            userSkillRepo = userSkills;
        }

        /// <summary>
        /// Displays a list of the user's.
        /// </summary>
        /// <param name="showInactive">Indicates whether the inactive users should be displayed.</param>
        /// <returns>The rendered view.</returns>
        [Authorize]
        public ViewResult Index(bool showInactive = false)
        {
            ViewBag.ShowEdit = User.IsInRole("SuperUser");
            IQueryable<User> users;
            if (showInactive)
                users = repository.Users.Where(u => !(u.Login.Equals("baseUser")));
            else
                users = repository.Users.Where(u => u.StatusRep != 2 && !(u.Login.Equals("baseUser")));
            ViewBag.showInactive = showInactive;
            users.OrderBy(u => u.FirstName);
            return View(users);
        }

        //
        // GET: /Default1/Details/5
        /// <summary>
        /// Displays details of a chosen user.
        /// </summary>
        /// <param name="id">The ID of the user, whose details will be displayed.</param>
        /// <returns>The rendered details view.</returns>
        [Authorize(Roles = "Manager, SuperUser")]
        public ViewResult Details(int id = 0)
        {
            User user = null;
            if (id == 0)
            {
                user = repository.Users.Single(u => u.Login.Equals(User.Identity.Name));
            }
            else
            {
                user = repository.Users.SingleOrDefault(u => u.UserID == id);
            }
            ViewBag.UserSkills = (user.User_Skills.Count > 0 ? true : false);
            ViewBag.UserTasks = (user.Tasks.Count > 0 ? true : false);

            var previousMonth = DateTime.Now.AddMonths(-1);
            //Get the worked hours from the previous month
            ViewBag.LastMonth = user.WorkingHoursEntries
                .Where(x => x.Date.Year == previousMonth.Year && x.Date.Month == previousMonth.Month)
                .Sum(x => x.Hours);

            //Get the worked hours from this month
            ViewBag.ThisMonth = user.WorkingHoursEntries
                .Where(x => x.Date.Year == DateTime.Now.Year && x.Date.Month == DateTime.Now.Month)
                .Sum(x => x.Hours);

            var firstDayOfThisWeek = DateTime.Now.AddDays(-(int)DateTime.Now.DayOfWeek).Date;
            //Get the worked hours from this week
            ViewBag.ThisWeek = user.WorkingHoursEntries
                .Where(x => x.Date.CompareTo(DateTime.Now) <= 0 && x.Date.CompareTo(firstDayOfThisWeek) >= 0)
                .Sum(x => x.Hours);
            return View(user);
        }

        //
        // GET: /User/Create
        /// <summary>
        /// Displays a form to create a new user.
        /// </summary>
        /// <returns>The rendered view.</returns>
        [Authorize(Roles = "SuperUser")]
        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /User/Create
        /// <summary>
        /// Handles the user creation request.
        /// </summary>
        /// <param name="user">The user entity which should be added after validation to the database.</param>
        /// <returns>Redirects to the user list on success, displays errors on the form otherwise.</returns>
        [Authorize(Roles = "SuperUser")]
        [HttpPost]
        public ActionResult Create(SimpleTaskManager.Domain.Entities.User user)
        {
            //if user with such a name already exists return to the view
            if (ModelState.IsValidField("Login") && repository.Users.Where(x => x.Login.ToLower().Equals(user.Login.ToLower())).Count() > 0)
            {
                ModelState.AddModelError("Login", "That login is already taken.");
            }

            if (ModelState.IsValid)
            {
                repository.CreateUser(user, passwordEncrypter);
                return RedirectToAction("Index");
            }

            return View(user);
        }

        //
        // GET: /User/Edit/5
        /// <summary>
        /// Displays a form to allow editing for a user entity.
        /// </summary>
        /// <param name="id">The ID of the user who will be edited, 0 indicates the current user.</param>
        /// <returns>The rendered edit view.</returns>
        [Authorize]
        [ActionName("Edit")]
        public ActionResult Edit(int id = 0)
        {
            ViewBag.Admin = User.IsInRole("SuperUser");
            ViewBag.Manager = User.IsInRole("Manager");
            User user;
            if (id == 0 || User.IsInRole("Worker"))
            {
                string username = User.Identity.Name;
                user = repository.Users.Single(x => x.Login.Equals(username));
            }
            else
            {
                user = repository.Users.Single(u => u.UserID == id);
            }

            ViewBag.CanChangePassword = ViewBag.Admin || user.Login.Equals(User.Identity.Name);
            return View(user);
        }

        //
        // POST: /User/Edit/5
        /// <summary>
        /// Handles the edits made to a user entity.
        /// </summary>
        /// <param name="user">The entity which was edited.</param>
        /// <returns>Redirects to the user list on success, displays errors on the form otherwise.</returns>
        [Authorize]
        [HttpPost]
        public ActionResult Edit(User user)
        {
            ViewBag.Admin = User.IsInRole("SuperUser");
            ViewBag.Manager = User.IsInRole("Manager");
            //if user with such a name already exists return to the view
            if (ModelState.IsValidField("Login") &&
                repository.Users.Where(x => x.Login.ToLower().Equals(user.Login.ToLower()) && x.UserID != user.UserID).Count() > 0)
            {
                ModelState.AddModelError("Login", "That login is already taken.");
            }

            if (ModelState.IsValid)
            {
                repository.EditUser(user);
                return RedirectToAction("Index");
            }

            ViewBag.CanChangePassword = ViewBag.Admin || user.Login.Equals(User.Identity.Name);
            return View(user);
        }

        //
        // GET: /User/ChangePassword/5
        /// <summary>
        /// Generates the view which allows the user to change her password.
        /// </summary>
        /// <param name="id">The id of the user, whose password can be changed. 0 or empty means the logged in user.</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult ChangePassword(int id = 0)
        {
            User user;
            if (id == 0)
            {
                //Get the user entity who is logged in
                user = repository.Users.Single(u => u.Login.Equals(User.Identity.Name));
            }
            else
            {
                //Get the user by ID
                user = repository.Users.Single(u => u.UserID == id);
            }
            //A user can change only her own password, except for SuperUsers
            if (User.IsInRole("SuperUser") || user.Login.Equals(User.Identity.Name))
            {
                ChangePasswordModel model = new ChangePasswordModel();
                model.UserId = user.UserID;
                return View(model);
            }
            return View("InsufficientPermissions");
        }

        //
        // POST: /User/ChangePassword/5
        /// <summary>
        /// Changes the password for a user.
        /// </summary>
        /// <param name="model">The ChangePasswordModel originating from the form on the ChangePassword view</param>
        /// <returns>Redirect to the user's edit page on success.
        /// Otherwise displays the ChangePassword view showing the validation errors.</returns>
        [Authorize]
        [HttpPost]
        public ActionResult ChangePassword(ChangePasswordModel model)
        {
            //Get the user entity
            var user = repository.Users.Single(u => u.UserID == model.UserId);
            //Check permissions
            if (!(User.IsInRole("SuperUser") || user.Login.Equals(User.Identity.Name)))
            {
                return View("InsufficientPermissions");
            }
            //Check if the old password is correct
            if (ModelState.IsValidField("OldPassword") && 
                    !user.Password.Equals(passwordEncrypter.HashPassword(model.OldPassword)))
            {
                ModelState.AddModelError("OldPassword", "The password is not correct.");
            }
            //Check if the new and confirmation passwords equal
            if (ModelState.IsValidField("NewPassword") && ModelState.IsValidField("ConfirmPassword")
                && !model.NewPassword.Equals(model.ConfirmPassword))
            {
                ModelState.AddModelError("ConfirmPassword", "The passwords don't match.");
            }

            if (ModelState.IsValid)
            {
                //Change the password if everything is fine
                repository.ChangeUserPassword(user, model.NewPassword, passwordEncrypter);
                return RedirectToAction("Edit", new { id = 0 });
            }
            return View(model);
        }

        //
        // GET: /User/Delete/5
        /// <summary>
        /// Asks for confirmation on the deletion of a user
        /// </summary>
        /// <param name="id">The ID of the user who would be deleted.</param>
        /// <returns>The rendered confirmation view.</returns>
        [Authorize(Roles="SuperUser")]
        public ActionResult Delete(int id)
        {
            return View(repository.Users.Single(u => u.UserID == id));
        }

        //
        // POST: /User/Delete/5
        /// <summary>
        /// Deletes the user after the confirmation.
        /// </summary>
        /// <param name="id">The ID of the user who will be deleted.</param>
        /// <returns>Redirects to the user list.</returns>
        [Authorize(Roles = "SuperUser")]
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {
            repository.DeleteUser(id);
            return RedirectToAction("Index");
        }


        //
        //GET: User/Hours
        /// <summary>
        /// Displays the user's working hour entries.
        /// </summary>
        /// <returns>The rendered view.</returns>
        [Authorize]
        public ActionResult Hours(int id = 0)
        {
            User user = null;
            if (id == 0)
            {
                string username = User.Identity.Name;
                user = repository.Users.Single(x => x.Login.Equals(username));
                ViewBag.DisplayEdit = true;
            }
            else
            {
                user = repository.Users.Single(x => x.UserID == id);
                ViewBag.DisplayEdit = false;
            }
            SimpleTaskManager.WebUI.Models.WorkingHoursModel hours = new SimpleTaskManager.WebUI.Models.WorkingHoursModel();
            hours.UserTasks = new SelectList(user.Tasks.ToList(), "TaskID", "Name");
            hours.Date = DateTime.Now;
            hours.UserID = user.UserID;
            return View(hours);
        }

        //
        //Post: User/AddHours
        /// <summary>
        /// Adds working hours to the user
        /// </summary>
        /// <param name="workingHours">The working hour entry which will be added to the current user.</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        [HttpPost, ActionName("Hours")]
        public ActionResult Hours(SimpleTaskManager.WebUI.Models.WorkingHoursModel workingHours)
        {
            if (ModelState.IsValid)
            {
                WorkingHoursEntry hoursEntry = new WorkingHoursEntry();
                hoursEntry.Hours = workingHours.Hours;
                hoursEntry.Date = workingHours.Date;
                hoursEntry.UserID = workingHours.UserID;
                hoursEntry.TaskID = workingHours.TaskID;
                hoursRep.Add(hoursEntry);
                SimpleTaskManager.WebUI.Models.WorkingHoursModel nextHours = new SimpleTaskManager.WebUI.Models.WorkingHoursModel();
                User user = repository.Users.Single(x => x.UserID == workingHours.UserID);
                ViewBag.DisplayEdit = user.Login.Equals(User.Identity.Name);
                nextHours.UserTasks = new SelectList(user.Tasks.ToList(), "TaskID", "Name");
                nextHours.Date = DateTime.Now;
                return View(nextHours);
            }
            return View(workingHours);
        }

        /// <summary>
        /// Partial view with the list of user's working hours
        /// </summary>
        /// <returns>PartialView with working hours of chosen user</returns>
        [Authorize]
        public PartialViewResult WorkingHoursList(int id = 0)
        {
            User user = null;
            if (id == 0)
            {
                string username = User.Identity.Name;
                user = repository.Users.Single(x => x.Login.Equals(username));
                ViewBag.DisplayEdit = true;
            }
            else
            {
                user = repository.Users.Single(x => x.UserID == id);
                ViewBag.DisplayEdit = false;
            }
            //List<WorkingHoursEntry> hours = hoursRep.WorkingHoursEntries.Where(h => h.User.UserID == user.UserID).ToList();
            return PartialView(user);
        }

        /// <summary>
        /// Deletes a working hour entry.
        /// </summary>
        /// <param name="id">The ID of the entry which will be deleted.</param>
        /// <returns>Redirects to the list of the working hour entries.</returns>
        [Authorize]
        public ActionResult DeleteHours(int id)
        {
            hoursRep.Delete(id);
            return RedirectToAction("Hours");
        }

        /// <summary>
        /// Creates a list of the users with whom the logged in user works with.
        /// </summary>
        /// <returns>The rendered partial view list.</returns>
        [Authorize]
        public ActionResult RelatedUsers()
        {
            var user = repository.Users.Single(u => u.Login.Equals(User.Identity.Name));

            var userList = new List<RelatedUsersModel>();
            foreach (var task in user.Tasks.Where(t => t.Name != "Internal project").Union(user.LeaderOf))
            {
                foreach (var u in task.Group.Where(us => us.UserID != user.UserID))
                    userList.Add(new RelatedUsersModel(u));

                if (user.UserID != task.LeaderId)
                    userList.Add(new RelatedUsersModel(task.Leader));
            }

            return PartialView("_RelatedUsersPartial", userList);
        }

        /// <summary>
        /// Displays the current user's worked hours in the last 30 days.
        /// </summary>
        /// <returns>The rendered partial view.</returns>
        [Authorize]
        public ActionResult MyWorkingHours()
        {
            var user = repository.Users.Single(u => u.Login.Equals(User.Identity.Name));

            //get entries from the last 30 days
            var entries = user.WorkingHoursEntries.Where(w => new TimeSpan(DateTime.Now.Ticks - w.Date.Ticks).Days <= 30);
            ViewBag.WorkedHours = entries.Sum(e => e.Hours);
            return PartialView("_MyWorkingHoursPartial");
        }

        /// <summary>
        /// Creates a view which allows user to add new User_Skill
        /// </summary>
        /// <param name="id">id of the user</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult Skills(int id = 0)
        {
            User_Skill model = new User_Skill();
            model.UserId = (id != 0) ? id : repository.Users.Single(u => u.Login.Equals(User.Identity.Name)).UserID;
            model.Rating = 0;

            ViewBag.Skills = skillsRepo.Skills.ToList();
            var user = repository.Users.Single(u => u.UserID == model.UserId);
            ViewBag.UserName = user.FirstName + " " + user.LastName;

            return View(model);
        }

        /// <summary>
        /// Creates a new User_Skill if the submitted form was valid, else it displays page indicating what went wrong
        /// </summary>
        /// <param name="skill">User_Skill submitted by the form</param>
        /// <returns>View indicating whether the operation was successful</returns>
        [Authorize]
        [HttpPost, ActionName("Skills")]
        public ActionResult Skills(User_Skill skill)
        {
            if (ModelState.IsValid)
            {
                repository.AddSkill(skill.UserId, skill);

                return RedirectToAction("Skills", new { id = skill.UserId });
            }

            var user = repository.Users.Single(u => u.UserID == skill.UserId);
            ViewBag.UserName = user.FirstName + " " + user.LastName;
            return View(skill);
        }

        /// <summary>
        /// View with the list of chosen user's skills
        /// </summary>
        /// <param name="id">user id</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult SkillsList(int id = 0)
        {
            User user = null;
            if (id == 0)
            {
                string username = User.Identity.Name;
                user = repository.Users.Single(x => x.Login.Equals(username));
            }
            else
            {
                user = repository.Users.Single(x => x.UserID == id);
            }

            ViewBag.DisplayDelete = User.Identity.Name.Equals(user.Login);

            ViewBag.CanRate = User.IsInRole("SuperUser") || User.IsInRole("Manager") 
                || IsUserBossOfUser(repository.Users.Single(u => u.Login.Equals(User.Identity.Name)).UserID, id);

            return PartialView(user);
        }

        /// <summary>
        /// View for deleting chosen skill
        /// </summary>
        /// <param name="id">skill id</param>
        /// <returns>Skill view</returns>
        [Authorize]
        public ActionResult DeleteSkill(int id)
        {
            var skill = userSkillRepo.User_Skills.Single(s => s.User_SkillID == id);
            userSkillRepo.Delete(id);
            return RedirectToAction("Skills", new { id = skill.UserId });
        }

        /// <summary>
        /// View which allows users to rate his/her subordinate skill
        /// </summary>
        /// <param name="id">id of User_Skill to rate</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        public ActionResult RateSkill(int id)
        {
            var skill = userSkillRepo.User_Skills.Single(s => s.User_SkillID == id);

            return View(skill);
        }

        /// <summary>
        /// View which handles the submitted form in which User_Skill was rated
        /// </summary>
        /// <param name="skill">skill that was rated</param>
        /// <returns>The rendered view</returns>
        [Authorize]
        [HttpPost, ActionName("RateSkill")]
        public ActionResult RateSkill(User_Skill skill)
        {
            if (ModelState.IsValid)
            {
                skill.ApprovedById = repository.Users.Single(u => u.Login.Equals(User.Identity.Name)).UserID;
                userSkillRepo.EditSkill(skill);

                return RedirectToAction("Skills", new { id = skill.UserId });
            }
            return View(skill);
        }

        private bool IsUserBossOfUser(int bossId, int subordinateId)
        {
            var boss = repository.Users.Single(u => u.UserID == bossId);

            return boss.LeaderOf.Any(t => t.Group.Any(g => g.UserID == subordinateId)
                || t.Group.Any(g => IsUserBossOfUser(g.UserID, subordinateId))); 
        }
    }
}
